// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html

const { ccclass, property } = cc._decorator;

@ccclass
export default class SimpleScrollViewPlus extends cc.ScrollView {

    @property({
        tooltip: '分帧加载时间间隔',
    })
    duration: number = 1;

    private isLoadingFinished: boolean = true;

    private cbAfterSetData: Function = null;

    private dataList: any[] = [];
    private nodeList: any[] = [];
    private scriptNames: string[] = [];

    private lastTime: number = 0;

    onLoad() {
        this.dataList = [];
        this.nodeList = [];
        this.scriptNames = [];

        this.node.on("scroll-ended", this.onScrollEnded, this);
        this.node.on("scrolling", this.onScrolling, this);
    }

    onDestroy() {
        this.node.off("scroll-ended", this.onScrollEnded, this);
        this.node.off("scrolling", this.onScrolling, this);
    }

    public init() {
        this.dataList = [];
        this.nodeList = [];
        this.scriptNames = [];
    }

    public add(node: cc.Node, data: any, scriptName: string) {
        this.nodeList.push(node);
        this.dataList.push(data);
        this.scriptNames.push(scriptName);
    }

    public async create(data: { cbAfterSetData?: Function } = null) {

        if (!this.isLoadingFinished) {
            return;
        }

        this.content.destroyAllChildren();

        if (data) {
            this.cbAfterSetData = data.cbAfterSetData;
        }

        this.isLoadingFinished = false;

        await this.executePreFrame(this.getItemGenerator(this.dataList.length), this.duration);
    }

    public updateDataList(dataList: any[]) {
        this.dataList = dataList;

        for (let i = 0; i < dataList.length; i++) {
            let item: cc.Node = this.content.getChildByName("listItem" + i);

            if (item) {
                let script = item.getComponent(this.scriptNames[i]);
                if (script && script.setData) {
                    script.setData(this.dataList[i]);
                }
            }
        }
    }

    public deleteItem(index: number) {
        let itemNode = this.content.getChildByName('listItem' + index);
        itemNode && itemNode.destroy();
        if (this.nodeList[index]) {
            this.nodeList[index].destroy();
            this.nodeList[index] = null;
        }

        if (this.dataList[index]) {
            this.dataList[index] = null;
        }

        if (this.scriptNames[index]) {
            this.scriptNames[index] = "";
        }
        // this.nodeList.splice(index, 1);
        // this.scriptNames.splice(index, 1);
        // this.dataList.splice(index, 1);
    }

    public updateItem(index: number, data: any, scriptName: string) {
        let itemNode = this.content.getChildByName('listItem' + index);
        if (itemNode) {
            itemNode.getComponent(scriptName).setData(data);
        }
    }

    public deleteAll() {
        this.dataList.length = 0;
        this.nodeList.length = 0;
        this.scriptNames.length = 0;

        this.content.destroyAllChildren();
    }

    public scrollToIndex(index: number, seconds: number = 0.2) {

        if (!this.isLoadingFinished) {
            return;
        }

        cc.log("index:", index);
        let childCount = this.content.childrenCount;
        cc.log("childCount:", childCount);

        index += 1;

        if (index < 1) {
            cc.log("index 过小");
            index = 1;
        }
        else if (index > childCount) {
            cc.log("index 过大");
            index = childCount;
        }

        let item = this.content.getChildByName('listItem' + index);
        if (item) {
            let layoutComp: cc.Layout = this.content.getComponent(cc.Layout);
            if (layoutComp) {
                if (layoutComp.type === cc.Layout.Type.VERTICAL && this.vertical && !this.horizontal) {
                    if (index == 1) {
                        this.scrollToTop(seconds);
                    } else if (index == childCount) {
                        this.scrollToBottom(seconds);
                    } else {
                        this.scrollToPercentVertical((childCount - index) / childCount, seconds);
                    }
                } else if (layoutComp.type === cc.Layout.Type.HORIZONTAL && !this.vertical && this.horizontal) {
                    if (index == 1) {
                        this.scrollToLeft(seconds);
                    } else if (index == childCount) {
                        this.scrollToBottomRight(seconds);
                    } else {
                        this.scrollToPercentHorizontal((childCount - index) / childCount, seconds);
                    }
                } else if (layoutComp.type === cc.Layout.Type.GRID) {
                    if (layoutComp.startAxis === cc.Layout.AxisDirection.HORIZONTAL) {
                        let contentSize = this.content.getContentSize();
                        let itemContentSize = item.getContentSize();
                        let rowEleCount = Math.floor((contentSize.width - layoutComp.paddingLeft - layoutComp.paddingRight + layoutComp.spacingX) / (itemContentSize.width + layoutComp.spacingX))
                        // cc.log("每行多少个：", rowEleCount);

                        let hang = Math.ceil(index / rowEleCount);
                        let totalHang = Math.ceil(childCount / rowEleCount);
                        this.scrollToPercentVertical((totalHang - hang) / totalHang, seconds);
                    } else {
                        let contentSize = this.content.getContentSize();
                        let itemContentSize = item.getContentSize();

                        let colEleCount = Math.floor((contentSize.height - layoutComp.paddingTop - layoutComp.paddingBottom + layoutComp.spacingY) / (itemContentSize.height + layoutComp.spacingY))
                        // cc.log("每列多少个：", colEleCount);

                        let lie = Math.ceil(index / colEleCount);
                        let totalLie = Math.ceil(childCount / colEleCount);
                        this.scrollToPercentHorizontal((totalLie - lie) / totalLie, seconds);
                    }

                } else {
                    cc.log("cc.Layout.Type不对");
                }
            }
        }
    }

    private onScrollEnded() {
        cc.log("onScrollEnded");
        this.improveDC();
    }

    private onScrolling() {
        let now = Date.now();
        if (now - this.lastTime < 100) {
            return;
        }
        cc.log("scolling");
        this.lastTime = now;

        this.improveDC();
    }

    //@ts-ignore
    private executePreFrame(generator: Generator, duration: number) {
        return new Promise((resolve, reject) => {
            let gen = generator;
            // 创建执行函数
            let execute = () => {
                // 执行之前，先记录开始时间
                let startTime = new Date().getTime();

                // 然后一直从 Generator 中获取已经拆分好的代码段出来执行
                for (let iter = gen.next(); ; iter = gen.next()) {
                    // 判断是否已经执行完所有 Generator 的小代码段，如果是的话，那么就表示任务完成
                    if (iter == null || iter.done) {
                        //@ts-ignore
                        resolve();
                        return;
                    }

                    // 每执行完一段小代码段，都检查一下是否已经超过我们分配的本帧，这些小代码端的最大可执行时间
                    if (new Date().getTime() - startTime > duration) {
                        // 如果超过了，那么本帧就不在执行，开定时器，让下一帧再执行
                        this.scheduleOnce(() => {
                            execute();
                        });
                        return;
                    }
                }
            };

            // 运行执行函数
            execute();
        });
    }

    private initItem(itemData: any, itemNode: cc.Node, scriptName: string) {

        if (!itemNode || (itemNode && !itemNode.isValid)) {
            return;
        }

        itemNode.name = "listItem" + this.content.childrenCount;
        itemNode.parent = this.content;
        let scriptCom = itemNode.getComponent(scriptName)
        if (scriptCom && scriptCom.setData) {
            scriptCom.setData(itemData);
        }
    }


    private *getItemGenerator(length: number) {
        for (let i = 0; i < length; i++) {
            yield this.initItem(this.dataList[i], this.nodeList[i], this.scriptNames[i]);
        }
        this.isLoadingFinished = true;
        console.log("ScrollView item加载完毕");
        this.scheduleOnce(() => {
            this.cbAfterSetData && this.cbAfterSetData();
            this.improveDC();
        });
    }

    // 优化DrawCall
    public improveDC() {
        if (this.content.childrenCount == 0) {
            return;
        }

        let svLeftBottomPoint: cc.Vec2 = this.node.parent.convertToWorldSpaceAR(
            // cc.v2(0,0)
            cc.v2(
                this.node.x - this.node.anchorX * this.node.width,
                this.node.y - this.node.anchorY * this.node.height
            )
        );

        // 求出 ScrollView 可视区域在世界坐标系中的矩形（碰撞盒）
        let svBBoxRect: cc.Rect = cc.rect(svLeftBottomPoint.x, svLeftBottomPoint.y, this.node.width, this.node.height);

        // 遍历 ScrollView Content 内容节点的子节点，对每个子节点的包围盒做和 ScrollView 可视区域包围盒做碰撞判断
        this.content.children.forEach((childNode: cc.Node) => {
            // 如果相交了，那么就显示，否则就隐藏
            let childNodeBBox = childNode.getBoundingBoxToWorld();
            if (childNodeBBox.intersects(svBBoxRect)) {
                if (childNode.opacity === 0) {
                    childNode.opacity = 255;
                }

            } else {
                if (childNode.opacity !== 0) {
                    childNode.opacity = 0;
                }
            }
        });
    }
}
